package ua.edu.nuos.jeetraining2012.cms.ui.window.dialogs.file_upload;

import com.vaadin.ui.*;
import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.predefined.StandardFileTypes;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.predefined.StandardGoogleApiVariables;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.auth.GoogleAuthException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.drive.GDriveFilePermissionException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.drive.GDriveUploadException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.transport.GoogleTransportException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.youtube.YouTubeUploadException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.drive.GoogleDriveUploader;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.youtube.YouTubeUploader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Map;

/**
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 08.06.13 Time: 19:06
 * @since 1.0
 */
public class FileUploadDialog extends Window implements Upload.Receiver, Upload.SucceededListener,
        Upload.StartedListener, Upload.FailedListener {

    /**
     * Define Upload catalog
     */
    final private static String UPLOAD_DIR = StandardGoogleApiVariables.UPLOAD_DIR;

    /**
     * Define layout
     */
    private VerticalLayout content;

    /**
     * Define uploading file type. Default set to presentation. And file will be uploaded to GDrive.
     */
    private String fileType = StandardFileTypes.FILE_TYPE_PRESENTATION;

    /**
     * Define title of the file, usually for {@link StandardFileTypes#FILE_TYPE_PRESENTATION} file type it
     * will use file name.
     * For {@link StandardFileTypes#FILE_TYPE_VIDEO} you should fill this field.
     */
    private String fileTitle;

    /**
     * Define description of the file.
     */
    private String fileDescription;

    /**
     * Define logger
     */
    private Logger logger = org.slf4j.LoggerFactory.getLogger(FileUploadDialog.class);

    // Constructors

    /**
     * Default constructor
     */
    public FileUploadDialog() {
        super("Upload video file.");
        logger.debug("VideoUploadDialog invocation.");
        lookAndFeel();
    }

    /**
     * <p>
     * Use this constructor to Build upload dialog with predefined file type. Depends
     * on predefined file type, location of uploading video will be changed.
     * </p>
     * <p>
     * If media type is {@link StandardFileTypes#FILE_TYPE_PRESENTATION}, file will be send to
     * Google Drive, either if media is {@link StandardFileTypes#FILE_TYPE_VIDEO}, file will be
     * send to YouTube.
     * </p>
     * <p>
     * {@code Default value:} {@link StandardFileTypes#FILE_TYPE_PRESENTATION}.
     * </p>
     *
     * @param fileType One of predefined types from {@link StandardFileTypes}.
     * @param fileInfo Map with fileTitle & fileDescription for YouTube.
     */
    public FileUploadDialog(String fileType, Map<String, String> fileInfo) {
        super("Upload video file.");
        this.fileType = fileType;
        logger.debug("FileUploadDialog invocation with file type: {}", fileType);
        lookAndFeel();
    }

    /**
     * Set just file type without any additional file info.
     * You can use this constructor for {@link StandardFileTypes#FILE_TYPE_PRESENTATION} file types.
     *
     * @param fileType One of {@link StandardFileTypes} file types.
     */
    public FileUploadDialog(String fileType) {
        this(fileType, null);
    }


    /**
     * Describe all elements and layouts here.
     */
    public void lookAndFeel() {
        // Define own window dimensions.
        this.setWidth("50%");
        this.setHeight("20%");
        this.center();
        content = new VerticalLayout();
        setContent(content);

        content.addComponent(addFileUploadField());
    }

    /**
     * File Upload element
     */
    private FormLayout addFileUploadField() {
        logger.debug("addFileUploadField() - strated.");

        final FormLayout fileFieldLayout = new FormLayout();
        final Upload upload = new Upload("Upload file", null);

        fileFieldLayout.setSizeUndefined();

        upload.setButtonCaption("Upload now.");
        upload.setReceiver((Upload.Receiver) this);
        upload.addSucceededListener((Upload.SucceededListener) this);
        upload.addStartedListener((Upload.StartedListener) this);
        upload.addFailedListener((Upload.FailedListener) this);

        fileFieldLayout.addComponent(upload);

        logger.debug("addFileUploadField() - finished.");
        return fileFieldLayout;
    }

    @Override
    public void uploadSucceeded(Upload.SucceededEvent succeededEvent) {
        logger.debug("uploadSucceeded() - started");

        Notification.show("Файл успешно загружен на сервер.", Notification.Type.TRAY_NOTIFICATION);
        logger.debug("Trying to upload file at GoogleDrive.");

        switch (fileType) {
            case StandardFileTypes.FILE_TYPE_PRESENTATION:
                sendFileToGDrive(succeededEvent.getFilename(), succeededEvent.getMIMEType());
                break;
            case StandardFileTypes.FILE_TYPE_VIDEO:
                if (fileDescription != null && !fileDescription.isEmpty()
                        && fileTitle != null && !fileTitle.isEmpty()) {
                    sendFileToYouTube(succeededEvent.getFilename(), fileTitle, fileDescription);
                } else {
                    sendFileToYouTube(succeededEvent.getFilename(), succeededEvent.getFilename(), "");
                }
                break;
        }

        this.close();

        logger.debug("uploadSucceeded() - finished.");
    }

    @Override
    public void uploadFailed(Upload.FailedEvent failedEvent) {
        logger.debug("uploadFailed() - started.");
        Notification.show("Загрузка файла не удалась.", Notification.Type.ERROR_MESSAGE);
        logger.debug("uploadFailed() - finished.");
    }

    @Override
    public void uploadStarted(Upload.StartedEvent startedEvent) {
    }

    @Override
    public OutputStream receiveUpload(String fileName, String mimeType) {
        logger.debug("receiveUpload() - started");
        if (!StandardGoogleApiVariables.isUploadDirExists(true)) {
            logger.error("Upload catalog does not exists and can not be created. Exiting");
            Notification.show("Каталог для загрузок " + StandardGoogleApiVariables.UPLOAD_DIR + " отсутствует " +
                    "и не может быть создан. Создайте его вручную и повторите попытку", Notification.Type.ERROR_MESSAGE);
            return null;
        }

        FileOutputStream out = null;
        File file = new File(UPLOAD_DIR + fileName);
        logger.debug("receiveUpload() Got upload file name: {}. Trying to upload...", file);

        try {
            out = new FileOutputStream(file);
            logger.debug("receiveUpload() Uploaded success.");
        } catch (FileNotFoundException e) {
            logger.error("Upload failed. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("receiveUpload() - finished with FileNotFoundException. Null will be returned.");
            return null;
        }

        logger.debug("receiveUpload() - finished.");
        return out;
    }

    /**
     * Wrapper of Google drive file uploader.
     *
     * @param fileName File to be uploaded
     * @param mimeType MIME Type of the file.
     * @return Map with file info in case of success or null.
     */
    private Map<String, String> sendFileToGDrive(String fileName, String mimeType) {
        logger.debug("sendFileToGDrive() - started.");

        Map<String, String> fileInfo = null;
        try {
            fileInfo = GoogleDriveUploader.uploadFileToDrive(fileName, mimeType);
        } catch (GDriveUploadException e) {
            logger.error("sendFileToGDrive() Can't upload file to GDrive. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        } catch (GDriveFilePermissionException e) {
            logger.error("sendFileToGDrive() Can't set permissions to uploaded file. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        } catch (GoogleTransportException e) {
            logger.error("sendFileToGDrive() Can't setup connection. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        } catch (GoogleAuthException e) {
            logger.error("sendFileToGDrive() Can't authorize on service. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        }

        if (fileInfo == null) {
            Notification.show("Не могу загрузить файл на Google Drive. Попробуйте позже или добавьте его вручную",
                    Notification.Type.ERROR_MESSAGE);
        }
        if (fileInfo != null && !fileInfo.isEmpty()) {
            Notification.show("Файл загружен на Google Drive.", Notification.Type.TRAY_NOTIFICATION);
        }

        logger.debug("sendFileToGDrive() - finished.");
        return fileInfo;
    }

    /**
     * Wrapper of YouTubeUploader.
     *
     * @param fileName File name.
     * @param title    Video title. For example, you can use material title.
     * @param desc     Video description. You can use material short description.
     * @return Map with video info in case of success or null.
     */
    private Map<String, String> sendFileToYouTube(String fileName, String title, String desc) {
        logger.debug("sendFileToYouTube() - started.");

        Map<String, String> videoInfo = null;

        try {
            videoInfo = YouTubeUploader.uploadFileToYouTube(fileName, title, desc);
        } catch (GoogleTransportException e) {
            logger.error("sendFileToYouTube() Can't setup connection. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        } catch (GoogleAuthException e) {
            logger.error("sendFileToYouTube() Can't authorize on service. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        } catch (YouTubeUploadException e) {
            logger.error("sendFileToYouTube() Can't upload file to YouTube. Error: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
        }

        if (videoInfo == null) {
            Notification.show("Не могу загрузить файл на YouTube. Попробуйте позже или добавьте его вручную",
                    Notification.Type.ERROR_MESSAGE);
        }
        if (videoInfo != null && !videoInfo.isEmpty()) {
            Notification.show("Файл загружен на YouTube", Notification.Type.TRAY_NOTIFICATION);
        }

        logger.debug("sendFileToYouTube() - finished.");
        return videoInfo;
    }

}
